/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2014-2019.
 * Description: This is the write interface for the livedump through the proc entry /proc/{PID}/coredump_trigger
 * Author: nixiaoming
 * Create: 2014-6-18
 */
#include <linux/proc_fs.h>
#include <linux/file.h>
#include <linux/printk.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/uaccess.h>
#include <linux/sched/mm.h>
#include <linux/idump.h>
#include <linux/idump_mm_extend.h>
#include "internal.h"
#include "proc_rtos.h"

#ifdef CONFIG_RTOS_IDUMP_TRIGGER
static ssize_t proc_coredump_trigger_write(struct file *file,
		const char __user *buf, size_t count, loff_t *ppos)
{
	char c;
	int ret = 0;
	struct task_struct *task = NULL;

	if (get_user(c, buf)) {
		pr_info("[idump][task:%s %d][func: %s] get_user fail\n", current->comm, current->pid, __func__);
		return -EFAULT;
	}
	if ((c - '0') != 1) {
		pr_info("[idump][task:%s %d][func: %s] EINVAL\n", current->comm, current->pid, __func__);
		return -EINVAL;
	}

	task = get_proc_task(file->f_path.dentry->d_inode);
	if (!task) {
		pr_info("[idump][task:%s %d][func: %s] get_proc_task fail\n", current->comm, current->pid, __func__);
		return -ESRCH;
	}

	pr_info("[idump] read to dump task:%s %d [func: %s]\n", task->comm, task->pid, __func__);
	/* livedump trigger */
	ret = do_dump_trigger(task);

	put_task_struct(task);
	return ret == 0 ? count : ret;
}

const struct file_operations proc_coredump_trigger_operations = {
	.write	= proc_coredump_trigger_write,
};
#endif

#if defined(CONFIG_RTOS_IDUMP_FORCE) || defined(CONFIG_RTOS_IDUMP_TRIGGER)
static ssize_t read_coredump_timeout(char __user *buf, const size_t count,
		loff_t *ppos, struct rtos_mm_struct *rtos_mm)
{
	int ret;
	size_t len;
	char buffer[18] = {0}; /* 18 > strlen("268435455 msec\n" + \0); */

	len = snprintf(buffer, sizeof(buffer), "%lu msec\n", rtos_mm->idump.timeout_msec);
	ret = simple_read_from_buffer(buf, count, ppos, buffer, len);
	return ret;
}

static ssize_t assign_coredump_timeout(const char __user *buf, const size_t count, struct rtos_mm_struct *rtos_mm)
{
	int ret;
	unsigned long timeout_msec;
	unsigned long timeout_max = 0x0fffffff; /* = 268435455 */
	char buffer[11] = {0};  /* 11 = strlen("268435455\n" + \0); */

	if (count > (sizeof(buffer) - 1)) {
		pr_info("[idump][task:%s %d][func: %s] EINVAL\n", current->comm, current->pid, __func__);
		return -EINVAL;
	}

	if (copy_from_user(buffer, buf, count)) {
		pr_info("[idump][task:%s %d][func: %s] copy_from_user fail\n", current->comm, current->pid, __func__);
		return -EINVAL;
	}

	/* /proc/<pid>/coredump_timeout must be set by decimal. */
	ret = kstrtoul(buffer, 10, &timeout_msec);
	if (ret != 0) {
		pr_info("[idump][task:%s %d][func: %s] kstrtoul fail\n", current->comm, current->pid, __func__);
		return ret;
	}

	if (timeout_msec > timeout_max) {
		pr_info("[idump][task:%s %d][func: %s] input val > timeout_max %lu\n",
				current->comm, current->pid, __func__, timeout_max);
		return -EINVAL;
	}
	rtos_mm->idump.timeout_msec = timeout_msec;
	return count;
}

static ssize_t proc_coredump_timeout(struct file *file, char __user *buf,
		size_t count, loff_t *ppos, int write)
{
	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
	struct mm_struct *mm = NULL;
	struct rtos_mm_struct *rtos_mm = NULL;
	int ret = -ESRCH;

	if (!task) {
		pr_info("[idump][task:%s %d][func: %s] get_proc_task fail\n", current->comm, current->pid, __func__);
		return ret;
	}
	mm = get_task_mm(task);
	if (!mm) {
		pr_info("[idump][task:%s %d][func: %s] get_task_mm fail\n", current->comm, current->pid, __func__);
		goto fail_put_task;
	}

	rtos_mm = mm_to_rtos_mm(mm);

	if (write)
		ret = assign_coredump_timeout(buf, count, rtos_mm);
	else
		ret = read_coredump_timeout(buf, count, ppos, rtos_mm);
	mmput(mm);
fail_put_task:
	put_task_struct(task);
	return ret;
}


/* read function for coredump_timeout proc interface */
static ssize_t proc_coredump_timeout_read(struct file *file, char __user *buf,
		size_t count, loff_t *ppos)
{
	return proc_coredump_timeout(file, buf, count, ppos, 0);
}

/* write function for coredump_timeout proc interface */
static ssize_t proc_coredump_timeout_write(struct file *file, const char __user *buf,
		size_t count, loff_t *ppos)
{
	/*
	 * We will not modify the buf, the forced conversion of const is just to
	 * normalize the read/write interface. If write, assign_coredump_timeout()
	 * will be used, where buf has been declared as const.
	 */
	return proc_coredump_timeout(file, (char __user *)buf, count, ppos, 1);
}

/* Read and write functions for /proc/<pid>/coredump_timeout file */
const struct file_operations proc_coredump_timeout_operations = {
	.read   = proc_coredump_timeout_read,
	.write  = proc_coredump_timeout_write,
};
#endif

static ssize_t proc_coredump_filter_type_read(struct file *file, char __user *buf,
		size_t count, loff_t *ppos)
{
	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
	struct mm_struct *mm = NULL;
	struct rtos_mm_struct *rtos_mm = NULL;
	char buffer[PROC_NUMBUF];
	size_t len;
	int ret;

	if (!task) {
		pr_info("[idump][task:%s %d][func: %s] get_proc_task fail\n", current->comm, current->pid, __func__);
		return -ESRCH;
	}

	ret = 0;
	mm = get_task_mm(task);
	if (mm) {
		rtos_mm = mm_to_rtos_mm(mm);
		len = snprintf(buffer, sizeof(buffer), "%u\n",
				rtos_mm->idump.dump_filter_type);
		mmput(mm);
		ret = simple_read_from_buffer(buf, count, ppos, buffer, len);
	}

	put_task_struct(task);

	return ret;
}

static ssize_t proc_coredump_filter_type_write(struct file *file,
		const char __user *buf,
		size_t count,
		loff_t *ppos)
{
	struct task_struct *task = NULL;
	struct mm_struct *mm = NULL;
	struct rtos_mm_struct *rtos_mm = NULL;
	int val;
	char c;
	int ret = 0;

	if (count > 2) { /* only accept 0 1 */
		pr_info("[idump][task:%s %d][func: %s] count > 2\n", current->comm, current->pid, __func__);
		return -EINVAL;
	}

	if (get_user(c, buf)) {
		pr_info("[idump][task:%s %d][func: %s] get_user fail\n", current->comm, current->pid, __func__);
		return -EFAULT;
	}

	val = c - '0';
	if (val < 0 || val >= FILTER_DUMP_TYPE_NUM) {
		pr_info("[idump][task:%s %d][func: %s] EINVAL\n", current->comm, current->pid, __func__);
		return -EINVAL;
	}

	task = get_proc_task(file->f_path.dentry->d_inode);
	if (!task) {
		pr_info("[idump][task:%s %d][func: %s] get_proc_task fail\n", current->comm, current->pid, __func__);
		return -ESRCH;
	}

	ret = -ESRCH;
	mm = get_task_mm(task);
	if (!mm) {
		pr_info("[idump][task:%s %d][func: %s] get_task_mm fail\n", current->comm, current->pid, __func__);
		goto out_no_mm;
	}
	rtos_mm = mm_to_rtos_mm(mm);
	rtos_mm->idump.dump_filter_type = val;
	mmput(mm);

	ret = count;
out_no_mm:
	put_task_struct(task);
	return ret;
}

const struct file_operations proc_coredump_filter_type_operations = {
	.read		= proc_coredump_filter_type_read,
	.write		= proc_coredump_filter_type_write,
};

/*
 * Read the contents of /proc/<pid>/coredump_stacksize file
 * which has the data written in format of x(pages). Where,
 * x is the integer which says how many pages the user
 * wants to core dump the stack. If user wants to dump the
 * used stack of main and child thread, the user has to echo
 * "used" to the proc interface. To dump the full stack, the
 * value of x is 0 for which, echo of file displays full_stack
 */
static ssize_t proc_coredump_stacksize_read(struct file *file, char __user *buf,
		size_t count, loff_t *ppos)
{
	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
	/* 30 is bigger than max len of strlen ( %lu pages ) */
	char buffer[30] = {0};
	size_t len = 0;
	int ret = -ESRCH;
	struct mm_struct *mm = NULL;
	struct rtos_mm_struct *rtos_mm = NULL;

	if (!task) {
		pr_info("[idump][task:%s %d][func: %s] get_proc_task fail\n", current->comm, current->pid, __func__);
		return ret;
	}

	mm = get_task_mm(task);
	if (!mm) {
		pr_info("[idump][task:%s %d][func: %s] get_task_mm fail\n", current->comm, current->pid, __func__);
		goto fail;
	}
	rtos_mm = mm_to_rtos_mm(mm);

	if (rtos_mm->idump.stacksize == IDUMP_USED_STACK) {
		len = snprintf(buffer, sizeof(buffer), "used(pages)\n");
	} else if (rtos_mm->idump.stacksize == IDUMP_FULL_STACK) {
		len = snprintf(buffer, sizeof(buffer), "full_stack\n");
	} else {
		len = snprintf(buffer, sizeof(buffer), "%u(pages)\n",
				rtos_mm->idump.stacksize);
	}
	ret = simple_read_from_buffer(buf, count, ppos, buffer, len);
	mmput(mm);
fail:
	put_task_struct(task);
	return ret;
}

static ssize_t assign_coredump_stacksize(const char *buffer, const size_t count, struct rtos_mm_struct *rtos_mm)
{
	int ret;
	unsigned long val;

	if ((strcmp(buffer, "used") == 0) || (strcmp(buffer, "used\n") == 0)) {
		/*
		 * in old usage, input "used" to set USED
		 * dump the used stack of  main and the child thread
		 */
		rtos_mm->idump.stacksize = IDUMP_USED_STACK;
		pr_info("[idump][task:%s %d][func: %s] EINVAL\n", current->comm, current->pid, __func__);
		return count;
	}

	ret = kstrtoul(buffer, 0, &val);
	if (ret != 0) {
		pr_info("[idump][task:%s %d][func: %s]  kstrtoul fail\n", current->comm, current->pid, __func__);
		return ret;
	}
	if (val > UINT_MAX)
		return -EINVAL;

	/*
	 * in old usage, 0 means FULL
	 * dumps the full stack
	 */
	if (val == 0)
		rtos_mm->idump.stacksize = IDUMP_FULL_STACK;
	else
		rtos_mm->idump.stacksize = (unsigned int)val;
	return count;
}

/*
 * write function for coredump_stacksize proc interface
 * /proc/<pid>/coredump_stacksize
 */
static ssize_t proc_coredump_stacksize_write(struct file *file,
		const char __user *buf,
		size_t count,
		loff_t *ppos)
{
	struct task_struct *task = NULL;
	char buffer[PROC_NUMBUF] = {0};
	int ret = -EINVAL;
	struct mm_struct *mm = NULL;
	struct rtos_mm_struct *rtos_mm = NULL;

	if (count > sizeof(buffer) - 1) {
		pr_info("[idump][task:%s %d][func: %s] count > sizeof(buffer) - 1\n", current->comm, current->pid, __func__);
		return ret;
	}

	ret = -EFAULT;
	if (copy_from_user(buffer, buf, count)) {
		pr_info("[idump][task:%s %d][func: %s]  copy_from_user fail\n", current->comm, current->pid, __func__);
		return ret;
	}

	ret = -ESRCH;
	task = get_proc_task(file->f_path.dentry->d_inode);
	if (!task) {
		pr_info("[idump][task:%s %d][func: %s]  get_proc_task fail\n", current->comm, current->pid, __func__);
		return ret;
	}
	mm = get_task_mm(task);
	if (!mm) {
		pr_info("[idump][task:%s %d][func: %s] get_task_mm fail\n", current->comm, current->pid, __func__);
		goto FAIL;
	}
	rtos_mm = mm_to_rtos_mm(mm);

	ret = assign_coredump_stacksize(buffer, count, rtos_mm);
	mmput(mm);
FAIL:
	put_task_struct(task);
	return ret;
}

/* Read and write functions for /proc/<pid>/coredump_stacksize file */
const struct file_operations proc_coredump_stacksize_operations = {
	.read   = proc_coredump_stacksize_read,
	.write  = proc_coredump_stacksize_write,
};


#ifdef CONFIG_RTOS_IDUMP_FORCE
/*
 * This function does the read of the mm->idump_force which is used
 * to signal mask of 64bit to dump the core forcibly if the signal
 * handler is registered
 */
static ssize_t proc_coredump_force_read(struct file *file, char __user *buf,
		size_t count, loff_t *ppos)
{
	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
	struct mm_struct *mm = NULL;
	struct rtos_mm_struct *rtos_mm = NULL;
/*
 * Max number of char size that fed
 * into coredump_force, aligned to 4 byte
 */
#define IDUMP_FORCE_SZ 24
	char buffer[IDUMP_FORCE_SZ] = {0};
	int ret = -ESRCH;

	if (!task)
		goto out_no_task;

	mm = get_task_mm(task);
	if (!mm)
		goto out_no_mm;
	rtos_mm = mm_to_rtos_mm(mm);

	/*
	 * Read the bitmapped value in mm->idump_force which wil be in chunks
	 * of 32bit. Each 32-bit chunks will be separated with a comma
	 */
	ret = snprintf(buffer, IDUMP_FORCE_SZ, "%*pb", IDUMP_SIG_NR, rtos_mm->idump.sig_force);
	/*
	 * Copy the read chunks of 32 bit to user space. Hence, displays the
	 * the value as "xxxxxxxx,xxxxxxxx"
	 */
	ret = simple_read_from_buffer(buf, count, ppos, buffer, ret);

	mmput(mm);
out_no_mm:
	put_task_struct(task);
out_no_task:
	return ret;
}
/*
 * This function does the write of the mm->idump_force which is used
 * to signal mask of 64bit to dump the core forcibly if the signal
 * handler is registered
 */
static DEFINE_SEMAPHORE(dump_force_mutex);
static ssize_t proc_coredump_force_write(struct file *file, const char __user *buf,
		size_t count, loff_t *ppos)
{
	 /* Note: The length of "ffffffff,ffffffff\0" is 18 */
#define IDUMP_SIG_LEN 18
	/* Declares the 2 unsigned longs on 32 bit machine */
	DECLARE_BITMAP(tmp_idump_force, IDUMP_SIG_NR);
	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
	struct mm_struct *mm = NULL;
	struct rtos_mm_struct *rtos_mm = NULL;
	int ret = -ESRCH;

	if (count > IDUMP_SIG_LEN)
		return -EINVAL;
#undef IDUMP_SIG_LEN
	if (!task)
		goto out_no_task;

	mm = get_task_mm(task);
	if (!mm)
		goto out_no_mm;
	rtos_mm = mm_to_rtos_mm(mm);

	/*
	 * Read the string fed by the user. The format of the input should
	 * be hex without 0x. When 800 is being sent, lower 32bit chunk will
	 * be updated with the 800 hex value to mm->idump_force. User has to
	 * echo ffffffff,ffffffff > /proc/pid/coredump_force to set all the
	 * bits. User has to set "signr" bit to force dump the core when
	 * "signr" is registered with an handler. THe core dump happens
	 * before the handler is being executed. However, for signal number,
	 * 9(SIGKILL), 19(SIGSTOP) its not applicable.
	 */
	ret = bitmap_parse_user(buf, count, tmp_idump_force, IDUMP_SIG_NR);
	if (ret)
		goto fail;

	ret = count;
	down(&dump_force_mutex);
	bitmap_copy(rtos_mm->idump.sig_force, tmp_idump_force, IDUMP_SIG_NR);
	up(&dump_force_mutex);
fail:
	mmput(mm);
out_no_mm:
	put_task_struct(task);
out_no_task:
	return ret;
}

/* Read and write functions for /proc/<pid>/coredump_force file */
const struct file_operations proc_coredump_force_operations = {
	.read   = proc_coredump_force_read,
	.write  = proc_coredump_force_write,
};
#endif
